D:\a\scloud-dns\scloud-dns\src\bin\dns_stress_test.rs
Line | Count | Source |
1 | | use std::net::SocketAddr; |
2 | | use std::sync::Arc; |
3 | | use std::sync::atomic::{AtomicU64, Ordering}; |
4 | | use std::time::{Duration, Instant}; |
5 | | use tokio::net::UdpSocket; |
6 | | |
7 | 0 | fn build_dns_query_a(qname: &str, id: u16) -> Vec<u8> { |
8 | 0 | let mut msg = Vec::with_capacity(512); |
9 | | |
10 | 0 | msg.extend_from_slice(&id.to_be_bytes()); |
11 | 0 | msg.extend_from_slice(&0x0100u16.to_be_bytes()); |
12 | 0 | msg.extend_from_slice(&1u16.to_be_bytes()); |
13 | 0 | msg.extend_from_slice(&0u16.to_be_bytes()); |
14 | 0 | msg.extend_from_slice(&0u16.to_be_bytes()); |
15 | 0 | msg.extend_from_slice(&0u16.to_be_bytes()); |
16 | | |
17 | 0 | for label in qname.trim_end_matches('.').split('.') { |
18 | 0 | let len = label.len(); |
19 | 0 | if len == 0 || len > 63 { |
20 | 0 | continue; |
21 | 0 | } |
22 | 0 | msg.push(len as u8); |
23 | 0 | msg.extend_from_slice(label.as_bytes()); |
24 | | } |
25 | 0 | msg.push(0); |
26 | | |
27 | 0 | msg.extend_from_slice(&1u16.to_be_bytes()); |
28 | 0 | msg.extend_from_slice(&1u16.to_be_bytes()); |
29 | | |
30 | 0 | msg |
31 | 0 | } |
32 | | |
33 | | #[tokio::main(flavor = "multi_thread")] |
34 | 0 | async fn main() -> anyhow::Result<()> { |
35 | | // Usage: |
36 | | // cargo run --release --bin dns_stress_test -- 127.0.0.1:5353 10 20 0 github.com. |
37 | | // 1 worker can handle 19 clients approx. (with my computer performance) |
38 | 0 | let args: Vec<String> = std::env::args().collect(); |
39 | 0 | if args.len() < 5 { |
40 | 0 | eprintln!( |
41 | | "Usage: {} <target ip:port> <duration_s> <clients> <sleep_ns> [qname]\n\ |
42 | | Example: {} 127.0.0.1:5353 10 200 0 example.com.", |
43 | 0 | args[0], args[0] |
44 | | ); |
45 | 0 | std::process::exit(2); |
46 | 0 | } |
47 | | |
48 | 0 | let target: SocketAddr = args[1].parse()?; |
49 | 0 | let duration_s: u64 = args[2].parse()?; |
50 | 0 | let clients: usize = args[3].parse()?; |
51 | 0 | let sleep_ns: u64 = args[4].parse()?; |
52 | 0 | let qname = args.get(5).map(|s| s.as_str()).unwrap_or("example.com."); |
53 | | |
54 | 0 | let sent = Arc::new(AtomicU64::new(0)); |
55 | 0 | let send_err = Arc::new(AtomicU64::new(0)); |
56 | | |
57 | 0 | let start = Instant::now(); |
58 | 0 | let deadline = start + Duration::from_secs(duration_s); |
59 | | |
60 | 0 | let mut handles = Vec::with_capacity(clients); |
61 | 0 | for client_idx in 0..clients { |
62 | 0 | let sent = sent.clone(); |
63 | 0 | let send_err = send_err.clone(); |
64 | 0 | let qname = qname.to_string(); |
65 | | |
66 | 0 | let h = tokio::spawn(async move { |
67 | 0 | let sock = UdpSocket::bind("0.0.0.0:0") |
68 | 0 | .await |
69 | 0 | .expect("bind client socket"); |
70 | 0 | sock.connect(target).await.expect("connect client socket"); |
71 | | |
72 | 0 | let mut id: u16 = (client_idx as u16).wrapping_mul(7919).wrapping_add(1); |
73 | | |
74 | 0 | while Instant::now() < deadline { |
75 | 0 | id = id.wrapping_add(1); |
76 | 0 | let q = build_dns_query_a(&qname, id); |
77 | | |
78 | 0 | match sock.send(&q).await { |
79 | 0 | Ok(_) => { |
80 | 0 | sent.fetch_add(1, Ordering::Relaxed); |
81 | 0 | } |
82 | 0 | Err(_) => { |
83 | 0 | send_err.fetch_add(1, Ordering::Relaxed); |
84 | 0 | } |
85 | | } |
86 | | |
87 | 0 | if sleep_ns > 0 { |
88 | 0 | tokio::time::sleep(Duration::from_nanos(sleep_ns)).await; |
89 | 0 | } |
90 | | } |
91 | 0 | }); |
92 | | |
93 | 0 | handles.push(h); |
94 | | } |
95 | | |
96 | 0 | let mut last = Instant::now(); |
97 | 0 | let mut last_sent = 0u64; |
98 | 0 | let mut last_err = 0u64; |
99 | | |
100 | 0 | while Instant::now() < deadline { |
101 | 0 | tokio::time::sleep(Duration::from_secs(1)).await; |
102 | | |
103 | 0 | let now = Instant::now(); |
104 | 0 | let dt = (now - last).as_secs_f64().max(1e-9); |
105 | | |
106 | 0 | let s = sent.load(Ordering::Relaxed); |
107 | 0 | let e = send_err.load(Ordering::Relaxed); |
108 | | |
109 | 0 | let ds = s - last_sent; |
110 | 0 | let de = e - last_err; |
111 | | |
112 | 0 | println!( |
113 | | "1s: sent/s={:.0} send_err/s={:.0} (total sent={})", |
114 | 0 | ds as f64 / dt, |
115 | 0 | de as f64 / dt, |
116 | | s |
117 | | ); |
118 | | |
119 | 0 | last = now; |
120 | 0 | last_sent = s; |
121 | 0 | last_err = e; |
122 | | } |
123 | | |
124 | 0 | for h in handles { |
125 | 0 | let _ = h.await; |
126 | | } |
127 | | |
128 | 0 | let elapsed = (Instant::now() - start).as_secs_f64().max(1e-9); |
129 | 0 | let s = sent.load(Ordering::Relaxed); |
130 | 0 | let e = send_err.load(Ordering::Relaxed); |
131 | | |
132 | 0 | println!("\n=== RESULT ==="); |
133 | 0 | println!("target: {target}, qname: {qname}, clients: {clients}, duration: {duration_s}s"); |
134 | 0 | println!("sent: {s} ({:.0}/s)", s as f64 / elapsed); |
135 | 0 | println!("send_err: {e} ({:.0}/s)", e as f64 / elapsed); |
136 | | |
137 | 0 | Ok(()) |
138 | 0 | } |